home *** CD-ROM | disk | FTP | other *** search
/ Libris Britannia 4 / science library(b).zip / science library(b) / PROGRAMM / DB_CLIPP / H245.ZIP / NFSRC21.ZIP / NWSEM.PRG < prev    next >
Text File  |  1992-10-17  |  18KB  |  574 lines

  1. /*
  2.  * File......: NWSEM.PRG
  3.  * Author....: Glenn Scott
  4.  * CIS ID....: 71620,1521
  5.  * Date......: $Date:   17 Oct 1992 16:28:22  $
  6.  * Revision..: $Revision:   1.4  $
  7.  * Log file..: $Logfile:   C:/nanfor/src/nwsem.prv  $
  8.  * 
  9.  * This is an original work by Glenn Scott and is placed in the
  10.  * public domain.
  11.  *
  12.  * Modification history:
  13.  * ---------------------
  14.  *
  15.  * $Log:   C:/nanfor/src/nwsem.prv  $
  16.  * 
  17.  *    Rev 1.4   17 Oct 1992 16:28:22   GLENN
  18.  * Leo cleaned up documentation blocks.
  19.  * 
  20.  *    Rev 1.3   08 Oct 1992 01:37:34   GLENN
  21.  * Added ft_nwsemUnlock() to complement ft_nwsemlock().  Modified
  22.  * the calling procedure for ft_nwsemlock() but it shouldn't break any
  23.  * existing code, although I doubt anyone's using it.
  24.  * 
  25.  * 
  26.  *    Rev 1.2   17 Aug 1991 16:11:46   GLENN
  27.  * Oops, I forgot to comment out some test code.
  28.  * 
  29.  *    Rev 1.1   15 Aug 1991 23:05:34   GLENN
  30.  * Forest Belt proofread/edited/cleaned up doc
  31.  * 
  32.  *    Rev 1.0   28 Jun 1991 00:44:14   GLENN
  33.  * Initial revision.
  34.  *
  35.  */
  36.  
  37.  
  38. // --------------------------------------------------------------
  39. //    Semaphore Package for Novell NetWare
  40. // --------------------------------------------------------------
  41.  
  42.  
  43. #include "ftint86.ch"
  44.  
  45. #define INT21    33
  46.  
  47. #xcommand DEFAULT <v1> TO <x1> [, <vN> TO <xN> ];
  48.       => IIF((<v1>)=NIL,<v1>:=<x1>,NIL) [; IF((<vN>)=NIL,<vN>:=<xN>,NIL)]
  49.  
  50. #define WAIT_SEMAPHORE    2
  51. #define SIGNAL_SEMAPHORE  3
  52. #define CLOSE_SEMAPHORE   4
  53.  
  54. // Sorry this test routine is pretty lame but it sort of gets
  55. // the point across
  56.  
  57. #ifdef FT_TEST
  58.  
  59.   #define INITIAL_SEMAPHORE_VALUE     2
  60.   #define WAIT_SECONDS                1
  61.  
  62.   function main()
  63.      local nInitVal, nRc, nHandle, nValue, nOpenCnt
  64.  
  65.      cls
  66.  
  67.      nInitVal := INITIAL_SEMAPHORE_VALUE
  68.      FT_NWSEMOPEN( "TEST", nInitVal, @nHandle, @nOpenCnt )
  69.  
  70.      qout( "Waiting ten seconds..." )
  71.      nRc := ft_nwSemWait( nHandle, 180 )
  72.      qout( "Final nRc value = " + STR( nRc ) )
  73.      inkey(0)
  74.      if nRc == 254
  75.         qout("Couldn't get the semaphore.  Try again.")
  76.         quit
  77.      end
  78.  
  79.      cls
  80.  
  81.      @ 24, 0 say "Any key to exit"
  82.      @ 0,  0 say "Handle: " + str( nHandle )
  83.  
  84.      ft_nwSemEx( nHandle, @nValue, @nOpenCnt )
  85.      while .t.
  86.         @ 23, 0 say "Semaphore test -> Open at [" + ;
  87.                     alltrim(str(nOpenCnt))        + ;
  88.                     "] stations, value is ["      + ;
  89.                     alltrim(str(nValue)) + "]"
  90.  
  91.         if inkey( WAIT_SECONDS ) != 0
  92.            exit
  93.         endif
  94.  
  95.         tone( nHandle,.5 )
  96.         ft_nwSemEx( nHandle, @nValue, @nOpenCnt )
  97.      end
  98.  
  99.      qout( "Signal returns: " + str( ft_nwsemSig( nHandle ) ) )
  100.      qout( "Close returns:  " + str( ft_nwsemClose( nHandle ) ) )
  101.  
  102.   return nil
  103.  
  104. #endif
  105.  
  106.  
  107. /*  $DOC$
  108.  *  $FUNCNAME$
  109.  *      FT_NWSEMOPEN()
  110.  *  $CATEGORY$
  111.  *      NetWare
  112.  *  $ONELINER$
  113.  *      Open or create a NetWare semaphore
  114.  *  $SYNTAX$
  115.  *      FT_NWSEMOPEN( <cName>, <nInitVal>, <@nHandle>, <@nOpenCnt> ) -> nRc
  116.  *  $ARGUMENTS$
  117.  *      <cName> is the semaphore name, maximum length is 127 characters.
  118.  *
  119.  *      <nInitVal> is the initial value for the semaphore.  It must start
  120.  *      as a positive number, to a maximum of 127.
  121.  *
  122.  *      <@nHandle> is the semaphore handle.  THIS MUST BE PASSED BY 
  123.  *      REFERENCE!  On exit, <nHandle> will contain a numeric value that
  124.  *      refers to the opened semaphore.  You will need it to pass to 
  125.  *      other semaphore functions!  PASS IT BY REFERENCE!
  126.  *
  127.  *      <@nOpenCnt> is the number of stations that have opened the 
  128.  *      semaphore.  THIS MUST BE PASSED BY REFERENCE! On exit, <nOpenCnt>
  129.  *      will contain a numeric value.
  130.  *  $RETURNS$
  131.  *      nRc, a numeric result code, as follows:
  132.  *
  133.  *            0 - success
  134.  *          254 - Invalid semaphore name length
  135.  *          255 - Invalid semaphore value
  136.  *     
  137.  *      <nHandle> will contain the semaphore handle, and 
  138.  *      <nOpenCnt> will contain the number of stations that have opened
  139.  *      the semaphore.
  140.  *  $DESCRIPTION$
  141.  *      A semaphore is simply a label that indirectly controls network
  142.  *      activity.  There is a semaphore name, which can be up to 127
  143.  *      characters, and an associated value, which can range from 0 to
  144.  *      127.
  145.  *
  146.  *      A semaphore can be used for many things, but is most often used
  147.  *      to limit the number of users in an application, and to control
  148.  *      access to a network resource.
  149.  *
  150.  *      A semaphore essentially allows you to place locks on resources
  151.  *      other than files.  
  152.  *
  153.  *      An application begins the process by calling FT_NWSEMOPEN().
  154.  *      If the semaphore doesn't exist, NetWare will create it.  
  155.  *      FT_NWSEMOPEN() returns a handle that is used in other semaphore
  156.  *      calls.
  157.  *
  158.  *      Applications use FT_NWSEMWAIT() to wait for a semaphore to 
  159.  *      become available.  FT_NWSEMWAIT() decrements the semaphore's
  160.  *      value by 1.  If the value > 0, then the application should 
  161.  *      be allowed to access the semaphore's resource.  If the value 
  162.  *      goes negative, then the application is placed in a queue.
  163.  *      How long your app is in the queue is determined by how you 
  164.  *      set the timeout parameter.  If you can't get the resource in 
  165.  *      the time you allot, you're let out of the queue and the 
  166.  *      value increments by 1 again.
  167.  *
  168.  *      When an application finishes with a semaphore, it should 
  169.  *      call FT_NWSEMSIG() to increment the value, and then 
  170.  *      FT_NWSEMCLOSE() to close the semaphore.  When the semaphore's
  171.  *      open count goes to 0, NetWare deletes it.
  172.  *
  173.  *      FT_NWSEMEX() can be used to examine the value and open count
  174.  *      without affecting them.
  175.  *
  176.  *      For an interesting discussion on the operating system aspects
  177.  *      of semaphores, check "Operating Systems Design and Implementation"
  178.  *      by A. Tanenbaum, page 60.  For more details on NetWare's 
  179.  *      semaphore facilities, refer to Charles Rose's "Programmer's 
  180.  *      Guide to NetWare".  The "Programmer's Guide" will make an 
  181.  *      excellent companion guide to the source code for all NetWare
  182.  *      functions in the Nanforum Toolkit.
  183.  *  $EXAMPLES$
  184.  *      LOCAL nInitVal, nRc, nHandle, nOpenCnt
  185.  *
  186.  *      nInitVal := 2
  187.  *      nRc      := FT_NWSEMOPEN( "Semaphore Test", nInitVal, ;
  188.  *                                @nHandle, @nOpenCnt )
  189.  *
  190.  *      IF nRc != 0
  191.  *        QOUT =: "Error: " + STR( nRc ) )
  192.  *        QUIT
  193.  *      ENDIF
  194.  *  $SEEALSO$
  195.  *      FT_NWSEMEX() FT_NWSEMWAIT() FT_NWSEMSIG() FT_NWSEMCLOSE() FT_NWSEMLOCK()
  196.  *  $END$
  197.  */
  198.  
  199. function ft_nwSemOpen( cName, nInitVal, nHandle, nOpenCnt )
  200.   local aRegs[ INT86_MAX_REGS ], cRequest, nRet
  201.  
  202.   default cName    to "",   ;
  203.           nInitVal to 0,    ;
  204.           nHandle  to 0,    ;
  205.           nOpenCnt to 0
  206.  
  207.  
  208.   cName    := iif( len( cName ) > 127, substr( cName, 1, 127 ), cName )
  209.   cRequest := chr( len( cName ) ) + cName
  210.  
  211.   aRegs[ AX ]      := makehi( 197 )                       // C5h
  212.   aRegs[ DS ]      := cRequest
  213.   aRegs[ DX ]      := REG_DS
  214.   aRegs[ CX ]      := nInitVal
  215.  
  216.   ft_int86( INT21, aRegs )
  217.  
  218.   nHandle  := bin2l( i2bin( aRegs[CX] ) + i2bin( aRegs[DX] ) )
  219.   nOpenCnt := lowbyte( aRegs[ BX ] )
  220.  
  221.   nRet := lowbyte( aRegs[AX] )
  222.  
  223.   return iif( nRet < 0, nRet + 256, nRet )
  224.  
  225.  
  226.  
  227.  
  228. /*  $DOC$
  229.  *  $FUNCNAME$
  230.  *      FT_NWSEMEX()
  231.  *  $CATEGORY$
  232.  *      NetWare
  233.  *  $ONELINER$
  234.  *      Examine a NetWare semaphore's value and open count
  235.  *  $SYNTAX$
  236.  *      FT_NWSEMEX( <nHandle>, <@nValue>, <@nOpenCnt> ) -> nRc
  237.  *  $ARGUMENTS$
  238.  *      <nHandle> is the semaphore handle, returned from a previous call
  239.  *      to FT_NWSEMOPEN().
  240.  *
  241.  *      <@nValue> will get the current semaphore value.  THIS NUMERIC
  242.  *      ARGUMENT MUST BE PASSED BY REFERENCE!
  243.  *
  244.  *      <@nOpenCnt> will get the current number of workstations 
  245.  *      that have opened the semaphore.  THIS NUMERIC ARGUMENT MUST BE
  246.  *      PASSED BY REFERENCE!
  247.  *  $RETURNS$
  248.  *      nRc, a numeric, as follows:
  249.  *
  250.  *            0 - success
  251.  *          255 - invalid semaphore handle
  252.  *
  253.  *      In addition, nValue will be set to the semaphore's current value,
  254.  *      and nOpenCnt will be set to the number of stations that have 
  255.  *      opened the semaphore.
  256.  *  $DESCRIPTION$
  257.  *      See the description for FT_NWSEMOPEN().
  258.  *  $EXAMPLES$
  259.  *    nInitVal := 2
  260.  *    nHandle  := 0
  261.  *    nOpenCnt := 0
  262.  *
  263.  *    FT_NWSEMOPEN( "Semaphore Test", nInitVal, @nHandle, @nOpenCnt )
  264.  *
  265.  *    nRc := FT_NWSEMWAIT( nHandle )
  266.  *        IF nRc == 254
  267.  *       QOUT( "All slots for this resource are currently in use" )
  268.  *       QUIT
  269.  *    ENDIF
  270.  *
  271.  *    FT_NWSEMEX( nHandle, @nValue, @nOpenCnt )
  272.  *    QOUT( "Semaphore test -> Open at [" + ;
  273.  *          ALLTRIM(STR(nOpenCnt))        + ;
  274.  *          "] stations, value is ["      + ;
  275.  *          ALLTRIM(STR(nValue)) + "]" )
  276.  *  $SEEALSO$
  277.  *      FT_NWSEMOPEN() FT_NWSEMWAIT() FT_NWSEMSIG() FT_NWSEMCLOSE() FT_NWSEMLOCK()
  278.  *  $END$
  279.  */
  280.  
  281.  
  282. function ft_nwSemEx( nHandle, nValue, nOpenCnt )
  283.   local aRegs[ INT86_MAX_REGS ], nRet
  284.  
  285.   default nHandle  to 0,  ;
  286.           nValue   to 0,  ;
  287.           nOpenCnt to 0
  288.  
  289.   aRegs[ AX ] :=  makehi( 197 ) + 1                         // C5h, 01h
  290.   aRegs[ CX ] :=  bin2i( substr( l2bin( nHandle ), 1, 2 ) )
  291.   aRegs[ DX ] :=  bin2i( substr( l2bin( nHandle ), 3, 2 ) )
  292.  
  293.   ft_int86( INT21, aRegs )
  294.  
  295.   #ifdef FT_TEST
  296.    
  297.      @ 5, 1 say highbyte( aregs[CX] )
  298.      @ 6, 1 say lowbyte( aregs[CX ] )
  299.  
  300.   #endif
  301.  
  302.   nValue   := aRegs[ CX ]
  303.   nOpenCnt := lowbyte( aRegs[ DX ] ) 
  304.   nRet     := lowbyte( aRegs[ AX ] )
  305.  
  306.   return iif( nRet < 0, nRet + 256, nRet )
  307.  
  308.  
  309. /*  $DOC$
  310.  *  $FUNCNAME$
  311.  *     FT_NWSEMWAIT()
  312.  *  $CATEGORY$
  313.  *     NetWare
  314.  *  $ONELINER$
  315.  *     Wait on a NetWare semaphore (decrement)
  316.  *  $SYNTAX$
  317.  *     FT_NWSEMWAIT( <nHandle> [, nTimeout ] ) -> nRc
  318.  *  $ARGUMENTS$
  319.  *     <nHandle> is the semaphore handle, returned from a previous call
  320.  *     to FT_NWSEMOPEN().
  321.  *
  322.  *     <nTimeOut> is an optional parameter telling how long you wish to
  323.  *     wait on this semaphore.  This is a numeric indicating the number
  324.  *     of clock ticks (approx 1/18 sec ) to wait.  A zero (the default)
  325.  *     means "don't wait."
  326.  *  $RETURNS$
  327.  *     nRc, a numeric, as follows:
  328.  *
  329.  *           0 - success
  330.  *         254 - timeout failure
  331.  *         255 - invalid semaphore handle
  332.  *  $DESCRIPTION$
  333.  *     See the description for the FT_NWSEMOPEN() function.
  334.  *  $EXAMPLES$
  335.  *    FT_NWSEMOPEN( "Semaphore Test", nInitVal, @nHandle, @nOpenCnt )
  336.  *
  337.  *    nRc := FT_NWSEMWAIT( nHandle )
  338.  *    IF nRc == 254
  339.  *       QOUT( "All slots for this resource are currently in use" )
  340.  *       QUIT
  341.  *    ENDIF
  342.  *  $SEEALSO$
  343.  *      FT_NWSEMOPEN() FT_NWSEMEX() FT_NWSEMSIG() FT_NWSEMCLOSE() FT_NWSEMLOCK()
  344.  *  $END$
  345.  */
  346.  
  347.  
  348.  
  349. function ft_nwSemWait( nHandle, nTimeout )
  350.   return  _ftnwsem( WAIT_SEMAPHORE, nHandle, nTimeout )
  351.  
  352.  
  353.  
  354. /*  $DOC$
  355.  *  $FUNCNAME$
  356.  *     FT_NWSEMSIG()
  357.  *  $CATEGORY$
  358.  *     NetWare
  359.  *  $ONELINER$
  360.  *     Signal a NetWare semaphore (increment)
  361.  *  $SYNTAX$
  362.  *     FT_NWSEMSIG( nHandle ) -> nRc
  363.  *  $ARGUMENTS$
  364.  *     <nHandle> is the semaphore handle, returned from a previous call
  365.  *     to FT_NWSEMOPEN().
  366.  *  $RETURNS$
  367.  *     nRc, a numeric, as follows
  368.  *
  369.  *          0 - success
  370.  *          1 - semaphore overflow ( value > 127 )
  371.  *        255 - invalid semaphore handle
  372.  *  $DESCRIPTION$
  373.  *      Use FT_NWSEMSIG() when your app has finished with the resource
  374.  *      locked by a semaphore.  This will increase the value (thus
  375.  *      making a slot available to another app).
  376.  *
  377.  *      For more information, see the description under FT_NWSEMOPEN().
  378.  *  $EXAMPLES$
  379.  *      QOUT( "Signal returns: " + STR( FT_NWSEMSIG( nHandle ) ) )
  380.  *  $SEEALSO$
  381.  *      FT_NWSEMOPEN() FT_NWSEMEX() FT_NWSEMWAIT() FT_NWSEMCLOSE() FT_NWSEMLOCK()
  382.  *  $END$
  383.  */
  384.  
  385.  
  386. function ft_nwSemSig( nHandle )
  387.   return  _ftnwsem( SIGNAL_SEMAPHORE, nHandle )
  388.  
  389.  
  390. /*  $DOC$
  391.  *  $FUNCNAME$
  392.  *     FT_NWSEMCLOSE()
  393.  *  $CATEGORY$
  394.  *     NetWare
  395.  *  $ONELINER$
  396.  *     Close a NetWare semaphore
  397.  *  $SYNTAX$
  398.  *     FT_NWSEMCLOSE( <nHandle> )  -> nRc
  399.  *  $ARGUMENTS$
  400.  *     <nHandle> is the semaphore handle, returned from a previous call
  401.  *     to FT_NWSEMOPEN().
  402.  *  $RETURNS$
  403.  *     nRc, a numeric, as follows:
  404.  *
  405.  *            0 - success
  406.  *          255 - invalid semaphore handle
  407.  *  $DESCRIPTION$
  408.  *     Call FT_NWSEMCLOSE() when the app is finished.  This decrements
  409.  *     the open count for the semaphore.  If the open count hits zero,
  410.  *     the semaphore is deleted by NetWare.
  411.  *  $EXAMPLES$
  412.  *     QOUT( "Close returns:  " + STR( FT_NWSEMCLOSE( nHandle ) ) )
  413.  *  $SEEALSO$
  414.  *     FT_NWSEMOPEN() FT_NWSEMEX() FT_NWSEMWAIT() FT_NWSEMSIG() FT_NWSEMLOCK()
  415.  *  $END$
  416.  */
  417.  
  418. function ft_nwSemClose( nHandle )
  419.   return  _ftnwsem( CLOSE_SEMAPHORE, nHandle )
  420.  
  421.  
  422. // ---------------------------------------------------------
  423. // _ftnwsem() - internal for the semaphore package
  424. // ---------------------------------------------------------
  425.  
  426. static function _ftnwsem( nOp, nHandle, nTimeout )
  427.   local aRegs[ INT86_MAX_REGS ],;
  428.         nRet
  429.  
  430.   default nOp      to SIGNAL_SEMAPHORE, ;
  431.           nHandle  to 0,                ;
  432.           nTimeout to 0
  433.  
  434.   aRegs[ AX ] :=  makehi( 197 ) + nOp
  435.   aRegs[ CX ] :=  bin2i( substr( l2bin( nHandle ), 1, 2 ) )
  436.   aRegs[ DX ] :=  bin2i( substr( l2bin( nHandle ), 3, 2 ) )
  437.   aRegs[ BP ] :=  nTimeout
  438.  
  439.  
  440.   ft_int86( INT21, aRegs )
  441.   nRet := lowbyte( aRegs[AX] )
  442.   nRet := iif( nRet < 0, nRet + 256, nRet )
  443.  
  444.   return nRet
  445.  
  446.  
  447.  
  448. /*  $DOC$
  449.  *  $FUNCNAME$
  450.  *     FT_NWSEMLOCK()
  451.  *  $CATEGORY$
  452.  *     NetWare
  453.  *  $ONELINER$
  454.  *     Perform a semaphore "lock"
  455.  *  $SYNTAX$
  456.  *     FT_NWSEMLOCK ( <cSemaphore>, <@nHandle> ) -> lRet
  457.  *  $ARGUMENTS$
  458.  *     <cSemaphore> is the name of a semaphore you want to "lock."
  459.  *     <nHandle> is the semaphore's handle, if you get the lock.
  460.  *     THIS MUST BE PASSED BY REFERENCE!
  461.  *  $RETURNS$
  462.  *     lRet == .t. if you get the lock, .f. if you don't.
  463.  *     If the lock succeeds, <nHandle> will contain the semaphore 
  464.  *     handle.  If it fails, the value of <nHandle> is undefined.
  465.  *
  466.  *  $DESCRIPTION$
  467.  *     FT_NWSEMLOCK() uses the Nanforum Toolkit's NetWare Semaphore API 
  468.  *     functions in order to provide a general purpose "lock" you can use in 
  469.  *     a NetWare environment.  
  470.  *
  471.  *     An interesting byproduct of NetWare's semaphore functions is
  472.  *     the "open count" which tells you how many connections have this
  473.  *     semaphore open.  This is different from the semaphore's _value_,
  474.  *     which is set when the semaphore is opened and changed with 
  475.  *     signal() and wait().  
  476.  *
  477.  *     The point of semaphores is that you don't care how many users
  478.  *     are using the resource; you merely wait on a semaphore until
  479.  *     the resource becomes available or you give up.  When you're done,
  480.  *     you signal it and off you go.
  481.  *
  482.  *     Back to the open count.  FT_NWSEMLOCK() opens the semaphore
  483.  *     as named in <cSemaphore>.  After it is opened, the open count
  484.  *     is checked.  If it is anything other than 1, that means someone
  485.  *     else has it (or you failed in your open) so the semaphore is
  486.  *     closed and the "lock" is refused.  If the value is 1, then your
  487.  *     app is that 1 station so the "lock" is granted.
  488.  *
  489.  *     You can use a semaphore lock to control access to anything
  490.  *     that Clipper's RLOCK() and FLOCK() can't help you with, such 
  491.  *     as text files written with the low level file i/o functions,
  492.  *     etc.  
  493.  *  $EXAMPLES$
  494.  *     LOCAL nHandle := 0
  495.  *     IF FT_NWSEMLOCK( "k:\apps\error.log", @nHandle )
  496.  *         // Note, you aren't actually LOCKING this file, you are
  497.  *         // just locking a semaphore by the same name.  As long as
  498.  *         // all apps that might be using this file are cooperating
  499.  *         // with the same kind of semaphore lock, you can effectively
  500.  *         // control access to the file.
  501.  *       ELSE
  502.  *         QOUT("Couldn't lock file.")
  503.  *       ENDIF
  504.  *       * Processing, then:
  505.  *       FT_NWSEMUNLOCK( nHandle )
  506.  *
  507.  *  $SEEALSO$
  508.  *     FT_NWSEMOPEN() FT_NWSEMEX() FT_NWSEMWAIT() FT_NWSEMSIG() FT_NWSEMUNLOCK()
  509.  *  $END$
  510.  */
  511.  
  512.  
  513.  
  514. function ft_nwSemLock( cSemaphore, nHandle )
  515.   local nRc
  516.   local nOpenCnt := 0
  517.   
  518.   nRc  := FT_NWSEMOPEN( cSemaphore, 0, @nHandle, @nOpenCnt )
  519.  
  520.   if nRc == 0
  521.      if nOpenCnt != 1
  522.         ft_nwSemClose( nHandle )
  523.      endif
  524.   endif
  525.  
  526.   return ( nOpenCnt == 1 )
  527.  
  528.  
  529. /*  $DOC$
  530.  *  $FUNCNAME$
  531.  *     FT_NWSEMUNLOCK()
  532.  *  $CATEGORY$
  533.  *     NetWare
  534.  *  $ONELINER$
  535.  *     "Unlock" a semaphore locked by FT_NWSEMLOCK()
  536.  *  $SYNTAX$
  537.  *     FT_NWSEMUNLOCK( <nHandle> ) -> lRet
  538.  *  $ARGUMENTS$
  539.  *     <nHandle> is the semaphore handle returned from FT_NWSEMLOCK()
  540.  *  $RETURNS$
  541.  *     lRet == .t. if you successfully unlock the semaphore, .f. if 
  542.  *     you don't.  If this call fails, it could be that you're passing
  543.  *     an invalid semaphore handle.
  544.  *  $DESCRIPTION$
  545.  *
  546.  *     This call unlocks a semaphore prevsiously locked via FT_NWSEMLOCK().
  547.  *     It is important that you get a valid semaphore handle from 
  548.  *     FT_NWSEMLOCK() before you use this call.  Make sure when you call
  549.  *     FT_NWSEMLOCK() that you pass a numeric parameter in for the handle
  550.  *     BY REFERENCE.
  551.  *  $EXAMPLES$
  552.  *     LOCAL nHandle := 0
  553.  *     IF FT_NWSEMLOCK( "k:\apps\error.log", @nHandle )
  554.  *         // Note, you aren't actually LOCKING this file, you are
  555.  *         // just locking a semaphore by the same name.  As long as
  556.  *         // all apps that might be using this file are cooperating
  557.  *         // with the same kind of semaphore lock, you can effectively
  558.  *         // control access to the file.
  559.  *       ELSE
  560.  *         QOUT("Couldn't lock file.")
  561.  *       ENDIF
  562.  *       * Processing, then:
  563.  *       FT_NWSEMUNLOCK( nHandle )
  564.  *
  565.  *  $SEEALSO$
  566.  *     FT_NWSEMOPEN() FT_NWSEMEX() FT_NWSEMWAIT() FT_NWSEMSIG() FT_NWSEMLOCK()
  567.  *  $END$
  568.  */
  569.  
  570.  
  571. function ft_nwSemUnLock( nHandle )
  572.   return ( ft_nwSemClose( nHandle ) == 0 )
  573.  
  574.